import type { Recordable } from '@/typings'

/**
 * 深拷贝函数
 * @param obj 要拷贝的对象
 * @returns 拷贝后的对象
 */
export function deepClone<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  if (obj instanceof Date) {
    return new Date(obj.getTime()) as T
  }

  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item)) as T
  }

  if (typeof obj === 'object') {
    const clonedObj = {} as T
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        clonedObj[key] = deepClone(obj[key])
      }
    }
    return clonedObj
  }

  return obj
}

/**
 * 对象深度合并
 * @param options 配置选项
 * @param options.target 目标对象
 * @param options.sources 源对象
 * @returns 合并后的对象
 */
export function deepMerge<T extends Recordable>(options: {
  target: T
  sources: T[]
}): T {
  const { target, sources } = options
  if (!sources.length)
    return target
  const source = sources.shift()

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key])
          Object.assign(target, { [key]: {} })
        deepMerge({ target: target[key], sources: [source[key]] })
      }
      else {
        Object.assign(target, { [key]: source[key] })
      }
    }
  }

  return deepMerge({ target, sources })
}

/**
 * 判断是否为对象
 * @param obj 要判断的值
 * @returns 是否为对象
 */
export function isObject(obj: any): obj is object {
  return obj !== null && typeof obj === 'object' && !Array.isArray(obj)
}

/**
 * 判断是否为数组
 * @param arr 要判断的值
 * @returns 是否为数组
 */
export function isArray(arr: any): arr is Array<any> {
  return Array.isArray(arr)
}

/**
 * 判断是否为函数
 * @param fn 要判断的值
 * @returns 是否为函数
 */
export function isFunction(fn: any): fn is (...args: any[]) => any {
  return typeof fn === 'function'
}

/**
 * 判断是否为字符串
 * @param str 要判断的值
 * @returns 是否为字符串
 */
export function isString(str: any): str is string {
  return typeof str === 'string'
}

/**
 * 判断是否为数字
 * @param num 要判断的值
 * @returns 是否为数字
 */
export function isNumber(num: any): num is number {
  return typeof num === 'number' && !Number.isNaN(num)
}

/**
 * 判断是否为布尔值
 * @param bool 要判断的值
 * @returns 是否为布尔值
 */
export function isBoolean(bool: any): bool is boolean {
  return typeof bool === 'boolean'
}

/**
 * 判断是否为 null 或 undefined
 * @param value 要判断的值
 * @returns 是否为 null 或 undefined
 */
export function isNullOrUndefined(value: any): value is null | undefined {
  return value === null || value === undefined
}

/**
 * 判断值是否为空
 * @param value 要判断的值
 * @returns 是否为空
 */
export function isEmpty(value: any): boolean {
  if (isNullOrUndefined(value))
    return true
  if (isString(value) || isArray(value))
    return value.length === 0
  if (isObject(value))
    return Object.keys(value).length === 0
  return false
}

/**
 * 获取值的类型
 * @param value 要获取类型的值
 * @returns 值的类型字符串
 */
export function getType(value: any): string {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}

/**
 * 格式化文件大小
 * @param options 配置选项
 * @param options.size 文件大小（字节）
 * @param options.decimals 小数位数，默认为 2
 * @returns 格式化后的文件大小字符串
 */
export function formatFileSize(options: { size: number, decimals?: number }): string {
  const { size, decimals = 2 } = options
  if (size === 0)
    return '0 Bytes'

  const k = 1024
  const dm = decimals < 0 ? 0 : decimals
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  const i = Math.floor(Math.log(size) / Math.log(k))

  return `${Number.parseFloat((size / k ** i).toFixed(dm))} ${sizes[i]}`
}

/**
 * 格式化数字，添加千分位分隔符
 * @param options 配置选项
 * @param options.num 要格式化的数字
 * @param options.separator 分隔符，默认为 ','
 * @returns 格式化后的数字字符串
 */
export function formatNumber(options: { num: number | string, separator?: string }): string {
  const { num, separator = ',' } = options
  const numStr = String(num)
  return numStr.replace(/\B(?=(\d{3})+(?!\d))/g, separator)
}

/**
 * 生成随机字符串
 * @param options 配置选项
 * @param options.length 字符串长度，默认为 8
 * @param options.chars 字符集，默认为字母和数字
 * @returns 随机字符串
 */
export function randomString(options: {
  length?: number
  chars?: string
} = {}): string {
  const {
    length = 8,
    chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
  } = options
  let result = ''
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length))
  }
  return result
}

/**
 * 安全的 JSON 解析
 * @param options 配置选项
 * @param options.str 要解析的 JSON 字符串
 * @param options.defaultValue 解析失败时的默认值
 * @returns 解析后的对象或默认值
 */
export function safeJsonParse<T = any>(options: {
  str: string
  defaultValue?: T
}): T | undefined {
  const { str, defaultValue } = options
  try {
    return JSON.parse(str)
  }
  catch {
    return defaultValue
  }
}

/**
 * 安全的 JSON 字符串化
 * @param options 配置选项
 * @param options.obj 要字符串化的对象
 * @param options.defaultValue 字符串化失败时的默认值，默认为 '{}'
 * @returns JSON 字符串或默认值
 */
export function safeJsonStringify(options: {
  obj: any
  defaultValue?: string
}): string {
  const { obj, defaultValue = '{}' } = options
  try {
    return JSON.stringify(obj)
  }
  catch {
    return defaultValue
  }
}

/**
 * 解析 URL 参数
 * @param url URL 字符串
 * @returns 参数对象
 */
export function parseUrlParams(url: string): Recordable {
  const params: Recordable = {}
  const urlObj = new URL(url, 'http://localhost')
  urlObj.searchParams.forEach((value, key) => {
    params[key] = value
  })
  return params
}

/**
 * 对象转 URL 参数字符串
 * @param options 配置选项
 * @param options.obj 参数对象
 * @returns URL 参数字符串
 */
export function objectToUrlParams(options: {
  obj: Recordable
}): string {
  const { obj } = options
  const params = Object.keys(obj)
    .filter(key => obj[key] !== undefined && obj[key] !== null && obj[key] !== '')
    .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(String(obj[key]))}`)
    .join('&')
  return params
}

/**
 * 数组去重
 * @param options 配置选项
 * @param options.arr 要去重的数组
 * @param options.key 如果是对象数组，指定去重的键名
 * @returns 去重后的数组
 */
export function uniqueArray<T>(options: {
  arr: T[]
  key?: keyof T
}): T[] {
  const { arr, key } = options
  if (!key) {
    return Array.from(new Set(arr))
  }

  const seen = new Set()
  return arr.filter((item) => {
    const value = item[key]
    if (seen.has(value)) {
      return false
    }
    seen.add(value)
    return true
  })
}

/**
 * 数组分组
 * @param options 配置选项
 * @param options.arr 要分组的数组
 * @param options.key 分组的键名
 * @returns 分组后的对象
 */
export function groupBy<T, K extends keyof T>(options: {
  arr: T[]
  key: K
}): Record<string, T[]> {
  const { arr, key } = options
  return arr.reduce((groups, item) => {
    const group = String(item[key])
    if (!groups[group]) {
      groups[group] = []
    }
    groups[group].push(item)
    return groups
  }, {} as Record<string, T[]>)
}

/**
 * 数组分页
 * @param options 配置选项
 * @param options.arr 要分页的数组
 * @param options.page 页码（从 1 开始）
 * @param options.pageSize 每页大小
 * @returns 分页后的数组
 */
export function paginate<T>(options: {
  arr: T[]
  page: number
  pageSize: number
}): T[] {
  const { arr, page, pageSize } = options
  const startIndex = (page - 1) * pageSize
  const endIndex = startIndex + pageSize
  return arr.slice(startIndex, endIndex)
}

/**
 * 数组排序
 * @param options 配置选项
 * @param options.arr 要排序的数组
 * @param options.key 排序的键名
 * @param options.order 排序方式，'asc' 为升序，'desc' 为降序
 * @returns 排序后的数组
 */
export function sortBy<T>(options: {
  arr: T[]
  key: keyof T
  order?: 'asc' | 'desc'
}): T[] {
  const { arr, key, order = 'asc' } = options
  return [...arr].sort((a, b) => {
    const aVal = a[key]
    const bVal = b[key]

    if (aVal < bVal) {
      return order === 'asc' ? -1 : 1
    }
    if (aVal > bVal) {
      return order === 'asc' ? 1 : -1
    }
    return 0
  })
}

/**
 * 数组求和
 * @param options 配置选项
 * @param options.arr 数字数组或对象数组
 * @param options.key 如果是对象数组，指定求和的键名
 * @returns 求和结果
 */
export function sum<T>(options: {
  arr: T[]
  key?: keyof T
}): number {
  const { arr, key } = options
  if (!key) {
    return (arr as number[]).reduce((total, num) => total + num, 0)
  }

  return arr.reduce((total, item) => {
    const value = Number(item[key]) || 0
    return total + value
  }, 0)
}

/**
 * 数组平均值
 * @param options 配置选项
 * @param options.arr 数字数组或对象数组
 * @param options.key 如果是对象数组，指定计算平均值的键名
 * @returns 平均值
 */
export function average<T>(options: {
  arr: T[]
  key?: keyof T
}): number {
  const { arr, key } = options
  if (arr.length === 0)
    return 0
  return sum({ arr, key }) / arr.length
}

/**
 * 数组最大值
 * @param options 配置选项
 * @param options.arr 数字数组或对象数组
 * @param options.key 如果是对象数组，指定比较的键名
 * @returns 最大值或包含最大值的对象
 */
export function max<T>(options: {
  arr: T[]
  key?: keyof T
}): T | number {
  const { arr, key } = options
  if (arr.length === 0)
    return 0

  if (!key) {
    return Math.max(...(arr as number[]))
  }

  return arr.reduce((maxItem, item) => {
    return Number(item[key]) > Number(maxItem[key]) ? item : maxItem
  })
}

/**
 * 数组最小值
 * @param options 配置选项
 * @param options.arr 数字数组或对象数组
 * @param options.key 如果是对象数组，指定比较的键名
 * @returns 最小值或包含最小值的对象
 */
export function min<T>(options: {
  arr: T[]
  key?: keyof T
}): T | number {
  const { arr, key } = options
  if (arr.length === 0)
    return 0

  if (!key) {
    return Math.min(...(arr as number[]))
  }

  return arr.reduce((minItem, item) => {
    return Number(item[key]) < Number(minItem[key]) ? item : minItem
  })
}

/**
 * 数组扁平化
 * @param options 配置选项
 * @param options.arr 要扁平化的数组
 * @param options.depth 扁平化深度，默认为 1
 * @returns 扁平化后的数组
 */
export function flatten<T>(options: {
  arr: any[]
  depth?: number
}): T[] {
  const { arr, depth = 1 } = options
  return depth > 0
    ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten({ arr: val, depth: depth - 1 }) : val), [])
    : arr.slice()
}

/**
 * 数组差集
 * @param options 配置选项
 * @param options.arr1 第一个数组
 * @param options.arr2 第二个数组
 * @returns 差集数组
 */
export function difference<T>(options: {
  arr1: T[]
  arr2: T[]
}): T[] {
  const { arr1, arr2 } = options
  return arr1.filter(item => !arr2.includes(item))
}

/**
 * 数组交集
 * @param options 配置选项
 * @param options.arr1 第一个数组
 * @param options.arr2 第二个数组
 * @returns 交集数组
 */
export function intersection<T>(options: {
  arr1: T[]
  arr2: T[]
}): T[] {
  const { arr1, arr2 } = options
  return arr1.filter(item => arr2.includes(item))
}

/**
 * 数组并集
 * @param options 配置选项
 * @param options.arr1 第一个数组
 * @param options.arr2 第二个数组
 * @returns 并集数组
 */
export function union<T>(options: {
  arr1: T[]
  arr2: T[]
}): T[] {
  const { arr1, arr2 } = options
  return Array.from(new Set(arr1.concat(arr2)))
}
